/**
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

package buses;

import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.Vector;

import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Item;
import javax.microedition.lcdui.List;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.TextBox;
import javax.microedition.lcdui.TextField;
import javax.microedition.midlet.MIDlet;
import javax.microedition.rms.RecordStoreException;

public class BusesMidlet extends MIDlet implements CommandListener{

	private static final String SERVER_URL_KEY = "Server-URL";
	private static final String INPUT_ENCODING_KEY = "Input-Encoding";
	
	private static Command searchLineCmd = new Command("חפש", Command.OK, 0);	
	private static Command displayScheduleCmd = new Command("הצג", Command.OK, 0);
	private static Command backCmd = new Command("חזור", Command.BACK, 0);
	private static Command backToStationsCmd = new Command("חזור", Command.BACK, 0);
	
	private static Form pleaseWaitForm = new Form("מחפש", new Item[]{new StringItem("","אנא המתן...")});
	private static List stationsList = new List("מוצא:", List.IMPLICIT);
	private static TextBox searchTxtbox = new TextBox("מספר קו", "", 3, TextField.NUMERIC);
	private static Alert notFoundAlert = new Alert(null, "הקו שביקשת לא נמצא", null, AlertType.INFO);
	private static Alert errorAlert = new Alert(null, null, null, AlertType.ERROR);
	
	private static String serverURL;
	private static String inputEncoding;
	
	private Line line;
	private Cache.ShortCache screenCache;
	
	public void startApp() {		
		try {
			// setting screens
			searchTxtbox.addCommand(searchLineCmd);
			searchTxtbox.setCommandListener(this);
			
			stationsList.setSelectCommand(displayScheduleCmd);
			stationsList.addCommand(backCmd);
			stationsList.setCommandListener(this);
			
			notFoundAlert.setTimeout(Alert.FOREVER);
			notFoundAlert.addCommand(backCmd);
			notFoundAlert.setCommandListener(this);
			
			errorAlert.setTimeout(Alert.FOREVER);
			errorAlert.addCommand(backCmd);
			errorAlert.setCommandListener(this);
			
			// initializing caches
			Cache.init();
			screenCache = new Cache.ShortCache(2);
			
			// getting application properties
			inputEncoding = getAppProperty(INPUT_ENCODING_KEY);
			serverURL = getAppProperty(SERVER_URL_KEY);
			
			// displaying the search screen
			displaySearch();
		}catch (Exception e) {
			error(e);
		}
	}

	public void commandAction(Command c, Displayable d) {
		try {
			if (c==searchLineCmd) {
				String s = ((TextBox)d).getString();
				if (s.length()==0) {
					alert("יש להזין מספר קו");
				} else {
					search(Integer.parseInt(s));
				}
			} else if (c==displayScheduleCmd) {
				displaySchedule(((List)d).getSelectedIndex());
			} else if (c==backCmd) {
				displaySearch();
			} else if (c==backToStationsCmd) {
				displayStations();
			}
		} catch (Exception e) {
			error(e);
		}
	}
	
	/**
	 * Searches for a line with a given line number
	 * @param lineNumber
	 * @throws IOException
	 * @throws RecordStoreException
	 */
	private void search(final int lineNumber) throws IOException, RecordStoreException, InterruptedException {
		
		// getting the line from cache
		line = Cache.retrieve(lineNumber);
		new Thread() {				
			public void run() {
				HttpConnection conn = null;
				try {
					if (line==null) {
						// line doesn't exist, fetching it from the server
						conn = (HttpConnection)Connector.open(serverURL +lineNumber);						
						switch (conn.getResponseCode()) {
						case 200:
							// line found, parsing and storing in cache
							line = new Line(conn, lineNumber);
							Cache.store(line);
						
						case 404:
							break;								
							
						default :					
							throw new IOException();
						}
					}
						
					if (line!=null) {
						// the line was found, either by the cache or the server
						displayStations();
					} else {
						// line was not found
						display(notFoundAlert);
					}
				} catch (Exception ioe) {
					error(ioe);
				} finally {
					try { if (conn!=null) conn.close(); } catch (IOException ioe2){}
				}
			}
		}.start();				
		
		display(pleaseWaitForm);
	}
	
	private void displaySearch() {
		searchTxtbox.setString("");
		display(searchTxtbox);
	}
	
	private void displayStations() throws IOException {
		displayList(stationsList, line.getStations());
	}
	
	private void displaySchedule(int index) throws IOException {
		
		Form f = null;
		
		if (screenCache.isCachingObject(line)) {
			// the line is the current screen caching object, so the cached screen is valid is exists
			f = (Form)screenCache.retrieve(index);
		} else {
			// the line is not the current screen caching object. setting it
			screenCache.setCachingObject(line);
		}
		
		if (f==null) {
			// the requested form was not found in the screen cache. creating it
			
			f = new Form("מועדי יציאה");
			f.addCommand(backToStationsCmd);
			f.setCommandListener(this);
			
			Vector schedule = line.getDepartures(index);
			
			f.append("התחלה: "+schedule.firstElement()+"\n");
			f.append("סיום: "+schedule.lastElement()+"\n");
			
			// searching for the next departure
			int offset = search(schedule, getDate());
			if (offset>0) {
                offset = offset-2;
			} else if (offset<0) {
                offset = schedule.size()-4;
			}

			
			// creating a StringBuffer to hold the four nearest departures (two in the past, two in the future)
			StringBuffer sb = new StringBuffer(24);
			for (int i=0; i<4 && i+offset<schedule.size(); i++) {
				sb.append(' ').append(schedule.elementAt(i+offset));
			}
			f.append("זמנים קרובים:"+sb.toString()+"\n");
			
			// adding all the departures
			f.append("\n\nהכל:\n");
			for (Enumeration e = schedule.elements(); e.hasMoreElements();) {
				f.append((String)e.nextElement()+"\n");
			}
			
			// storing the screen in the form cache
			screenCache.store(f, index);
		}
		
		// displaying the form
		display(f);
	}
	
	/**
	 * Fills a given List with the content of a given Vector as Strings, and displaying it
	 * @param l
	 * @param v
	 */
	private void displayList(List l, Vector v) {
		l.deleteAll();
		for (Enumeration e = v.elements(); e.hasMoreElements();) {
			l.append(e.nextElement().toString(), null);
		}							
		display(l);
	}
	
	/**
	 * Displaying a given Displayable
	 * @param d
	 */
	private void display(Displayable d) {
		Display.getDisplay(this).setCurrent(d);
	}
	
	private void error(Throwable t) {
		errorAlert.setString("error: "+t.getMessage());
		display(errorAlert);
	}
	
	private void alert(String s) {
		alert(s, 2);
	}
	
	private void alert(String s, int timeout) {
		Alert a = new Alert(null, s, null, null);
		a.setTimeout(timeout);
		display(a);
	}
	
	/**
	 * Returns a String representation of the currant time, in an HH:MM format 
	 * @return
	 */
	private String getDate() {
		Calendar c = Calendar.getInstance();
		c.setTime(new Date());
		int hours = c.get(Calendar.HOUR_OF_DAY);
		int minutes = c.get(Calendar.MINUTE);
		return (hours<10?"0":"")+hours+":"+(minutes<10?"0":"")+minutes;
	}
	
	/**
	 * Searches a Vector of times as Strings for the first one that occures after a given time.
	 * All the times are in an HH:MM format
	 * 
	 * @see getDate()
	 * @param v
	 * @param time
	 * @return
	 */
	private int search(Vector v, String time) {
		for (int i=0; i<v.size(); i++) {
			String s = (String)v.elementAt(i);
			if (compareTimes(s, time)>=0) {
				return i;
			}
		}
		
		return -1;
	}
	
	/**
	 * Compares two times in the HH:MM format
	 * @param s1
	 * @param s2
	 * @return
	 */
	private int compareTimes(String s1, String s2) {
		for (int i=0; i<s1.length(); i++) {
			char c1 = s1.charAt(i);
			char c2 = s2.charAt(i);
			
			if (c1!=c2) {
				return c1-c2;
			}
		}
		return 0;
	}
	
	/**
	 * Returns the input encoding expected by the server
	 */
	public static String getInputEncoding() {
		return inputEncoding;
	}
	
	public void destroyApp(boolean unconditional) {}
	public void pauseApp() {}
}
